﻿using EntityframeworkHelper.Data.EntityExtend;
using EntityFrameworkHelper.Repositories;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Threading.Tasks;

namespace EntityFrameworkHelper.UnitOfWorks
{
    /// <summary>
    ///     Class UnitOfWork.
    /// </summary>
    /// <typeparam name="TContext">The type of the t context.</typeparam>
    /// <typeparam name="TKey">The type of the t key.</typeparam>
    public class UnitOfWork<TContext, TKey> : IDisposable, IUnitOfWork<TKey, TContext>
        where TContext : DbContext, new()
    {
        /// <summary>
        ///     The repositories
        /// </summary>
        private readonly Dictionary<Type, object> repositories = new Dictionary<Type, object>();

        /// <summary>
        ///     The disposed
        /// </summary>
        private bool disposed;

        /// <summary>
        ///     Gets or sets the entities.
        /// </summary>
        /// <value>The entities.</value>
        private TContext Entities { get; set; }

        /// <summary>
        ///     Gets or sets the entity transaction.
        /// </summary>
        /// <value>The entity transaction.</value>
        private DbContextTransaction EntityTransaction { get; set; }

        #region IDisposable Members

        /// <summary>
        ///     Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        #endregion IDisposable Members

        #region IUnitOfWork<TKey,TContext> Members

        /// <summary>
        ///     Initializes the context.
        /// </summary>
        /// <returns>TContext.</returns>
        public TContext Create()
        {
            this.Entities = new TContext();
            this.EntityTransaction = this.Entities.Database.BeginTransaction();
            return this.Entities;
        }

        /// <summary>
        ///     Repositories this instance.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns>IRepository&lt;T&gt;.</returns>
        public IRepository<T> Repository<T>() where T : EntityBase<TKey>
        {
            if (this.repositories.Keys.Contains(typeof(T)))
            {
                return this.repositories[typeof(T)] as IRepository<T>;
            }
            IRepository<T> repo = new Repository<T, TContext, TKey>(this.Entities);
            this.repositories.Add(typeof(T), repo);
            return repo;
        }

        /// <summary>
        ///     Saves the changes.
        /// </summary>
        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
        public bool SaveChanges()
        {
            var changeResult = this.Entities.SaveChanges() > 0;
            if (changeResult)
            {
                this.EntityTransaction.Commit();
            }
            else
            {
                this.EntityTransaction.Rollback();
            }
            return changeResult;
        }

        /// <summary>
        ///     Saves the changes.
        /// </summary>
        /// <returns>Task.</returns>
        public async Task<bool> SaveChangesAsync()
        {
            var changeResult = await this.Entities.SaveChangesAsync() > 0;
            if (changeResult)
            {
                this.EntityTransaction.Commit();
            }
            else
            {
                this.EntityTransaction.Rollback();
            }
            return changeResult;
        }

        #endregion IUnitOfWork<TKey,TContext> Members

        /// <summary>
        ///     Releases unmanaged and - optionally - managed resources.
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    this.Entities.Dispose();
                }
            }
            this.disposed = true;
        }
    }
}